Flutter 能夠自動處理在其內部運作過程中出現的錯誤。比如渲染畫面、安排組件位置、建立使用者介面時,如果出現錯誤,Flutter 會自動攔截這些錯誤。但如果錯誤發生在框架管控的範圍之外,例如發送網路請求時或者讀取檔案時出錯,此時 Flutter 就無法自動攔截 catch 這些問題。但我們可以使用 PlatformDispatcher進行錯誤處理。
所有自動攔截到的錯誤都會被導向 FlutterError.onError,預設情況下會呼叫 FlutterError.presentError 方法將錯誤訊息輸出到裝置的日誌中。
但當從 IDE 執行時,情況不太一樣,IDE Inspector 會覆寫這個預設行為,讓錯誤也被導向到編輯器的控制台。如此一來開發者可以更方便的查詢這些錯誤資訊。
當錯誤是在建構時期發生的話會呼叫 ErrorWidget.builder 來建立一個替代性的組件,代替那個出錯的組件。在偵錯模式下,替代組件預設會顯示紅色錯誤訊息,在正式模式(Release mode) 則會顯示一個灰色背景。
其他 Flutter 管控範圍外的錯誤,會在 PlatfromDispatcher 的 error callback 中處理,預設只會輸出錯誤訊息。
我們可以自訂這些行為,一般是在 void main() 裡面設定。 下面我們將介紹各種錯誤類型。
若要讓你的應用程式在發布模式下,每當 Flutter 攔截到錯誤時立刻退出,你可以使用以下的處理程序:
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
  FlutterError.onError = (details) {
    FlutterError.presentError(details);
    if (kReleaseMode) {
      exit(1);
    }
    runApp(const MyApp());
  }
}
常量 kReleaseMode 用於判斷應用程式是否以發布模式編譯。這個處理程序也可以用來將錯誤回報給日誌記錄服務。
為了自訂錯誤組件用於在 builder 無法建立組件時顯示,可以使用 MaterialApp.builder:
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, widget) {
        Widget error = const Text('...rendering error...');
        if (widget is Scaffold || widget is Navigator) {
          error = Scaffold(body: Center(child: error));
        }
        ErrorWidget.builder = (errorDetails) => error;
        if (widget != null) return widget;
        throw StateError('widget is null');
      },
    );
  }
}
另一種常見的情況比如所在 onPressed 中呼叫了非同步函式:
OutlinedButton(
  child: const Text('Click me!'),
  onPressed: () async {
    const channel = MethodChannel('crashy-custom-channel');
    await channel.invokeMethod('blah');
  },
)
若 invokeMethod 拋出例外,此時 FlutterError.onError 無法自動攔截。這個使用需要使用 PlatformDispatcher.instance.onError
import 'package:flutter/material.dart';
import 'dart:ui';
void main() {
  MyBackend myBackend = MyBackend();
  PlatformDispatcher.instance.onError = (error, stack) {
    myBackend.sendError(error, stack);
    return true;
  };
  runApp(const MyApp());
}
假設你希望在任何異常發生時退出應用程式,並且在組件建構失敗時顯示一個自訂的錯誤組件 - 你可以基於以下程式碼片段來處理錯誤:
import 'package:flutter/material.dart';
import 'dart:ui';
Future<void> main() async {
  await myErrorsHandler.initialize();
  FlutterError.onError = (details) {
    FlutterError.presentError(details);
    myErrorsHandler.onErrorDetails(details);
  };
  PlatformDispatcher.instance.onError = (error, stack) {
    myErrorsHandler.onError(error, stack);
    return true;
  };
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, widget) {
        Widget error = const Text('...rendering error...');
        if (widget is Scaffold || widget is Navigator) {
          error = Scaffold(body: Center(child: error));
        }
        ErrorWidget.builder = (errorDetails) => error;
        if (widget != null) return widget;
        throw StateError('widget is null');
      },
    );
  }
}
若您已經使用了 BLoC 等狀態管理你後續可以參考 How I handle errors in Flutter。